package main

import "fmt"

type (
	Stack[T any] struct {
		top  *node[T]
		size int
	}
	node[T any] struct {
		prev *node[T]
		val  T
	}
)

func NewStack[T any]() *Stack[T] {
	return &Stack[T]{top: nil}
}

func (s *Stack[T]) Push(val T) {
	n := &node[T]{s.top, val}
	s.top = n
	s.size++
}

func (s *Stack[T]) IsEmpty() bool {
	return s.size == 0
}

// You can't return nil for 'any' type.
// If int is used as the type argument for T for example returning nil makes no sense.
// What you may do and what makes sense is return the zero value for the type argument used for T.
func (s *Stack[T]) Pop() T {
	var v T
	if s.IsEmpty() {
		return v
	}

	v = s.top.val
	s.top = s.top.prev
	s.size--

	return v
}

func (s *Stack[T]) Peek() T {
	return s.top.val
}

type Student struct {
	name string
	age  int
}

func main() {
	s := NewStack[Student]()
	for idx := 0; idx < 10; idx++ {
		s.Push(Student{age: idx + 10})
	}

	s.Push(Student{"Paul", 44})

	for i := 0; i < 5; i++ {
		fmt.Println(s.Pop())
	}

	fmt.Println()

	for !s.IsEmpty() {
		fmt.Println(s.Pop())
	}
}
